use crate::alloc::boxed::Box;
use crate::alloc::sync::Arc;
use crate::global::{CURRENT_THREAD, MAX_PRIORITY};
use crate::scheduler::rt_schedule;
use crate::thread::{rt_thread_weak, ObjThread};
use alloc::collections::LinkedList;
use rtt_kernel_mutex::KMutex;

struct RawMQ<T> {
    data: LinkedList<T>,
    threads: [LinkedList<Box<ObjThread>>; MAX_PRIORITY],
}

unsafe impl<T> Send for MQ<T> where T: Send {}
unsafe impl<T> Sync for MQ<T> where T: Send {}

pub struct MQ<T> {
    raw: Arc<KMutex<RawMQ<T>>>,
}

impl<T> Clone for MQ<T> {
    fn clone(&self) -> Self {
        MQ {
            raw: self.raw.clone(),
        }
    }
}

impl<T> MQ<T> {
    pub fn new() -> MQ<T> {
        const VOID_LIST: LinkedList<Box<ObjThread>> = LinkedList::new();
        MQ {
            raw: Arc::new(KMutex::new(RawMQ {
                data: LinkedList::new(),
                threads: [VOID_LIST; MAX_PRIORITY],
            })),
        }
    }

    pub fn MQueue() -> (MLibMQ<T>, MLibMQ<T>) {
        let a = MLibMQ::new();
        (a.clone(), a)
    }

    fn not_empty_list(&self) -> Option<usize> {
        let list = self.raw.lock();
        for i in 0..MAX_PRIORITY {
            if !list.threads[i].is_empty() {
                return Some(i);
            }
        }
        None
    }

    pub fn push(&self, data: T) {
        let mut raw = self.raw.lock();
        raw.data.push_back(data);

        match self.not_empty_list() {
            None => {}
            Some(i) => {
                let th = raw.threads[i].pop_front().unwrap();
                rt_thread_weak(th);
            }
        }
    }

    pub fn pop(&self) -> T {
        loop {
            let mut raw = self.raw.lock();
            if raw.data.is_empty() {
                let mut th = CURRENT_THREAD.lock();
                let cur_th = th.get_thread_set_sp().unwrap();
                let cur_th_pri = cur_th.get_priority();
                raw.threads[cur_th_pri].push_back(cur_th);
                rt_schedule();
            } else {
                return raw.data.pop_front().unwrap();
            }
        }
    }
}

pub type MLibMQ<T> = MQ<T>;
